/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import manager.PathManager;
import utils.Analyzer;
import utils.Utils;

/**
 *
 * @author wpc
 */
public class Queue {

    private int id;
    private double time;
    private int k;
    private double rate;
    private boolean isEnd;
    public static PathManager PATH_MANAGER;

    private List<Long> lossBatchList;//(*@ \label{java:queue:lossbatchlist} @*)
    private long nowLossBatchLength;
    private long receiveLossBatch;//(*@ \label{java:queue:receivelossbatch} @*)
    private Map<Integer, Double> nowDelay;//(*@ \label{java:queue:nowdelay} @*)
    private Map<Integer, List<Double>> delayBatchMap;
    private Map<Integer, Integer> receiveDelayBatchMap;//(*@ \label{java:queue:receivedelaybatch} @*)

    public Queue(int id, int k, double rate) {
        this(id, k, rate, false);
    }

    //Is End queue
    public Queue(int id) {
        this(id, 0, 0, true);
    }

    public Queue(int id, int k, double rate, boolean isEnd) {
        this.id = id;
        this.k = k;
        this.rate = rate;
        this.time = 0;
        this.isEnd = isEnd;
        this.nowLossBatchLength = 0;
        this.receiveLossBatch = 0;
        this.nowDelay =  new HashMap<Integer, Double>();
        this.lossBatchList = new LinkedList<Long>();
        this.delayBatchMap = new HashMap<Integer, List<Double>>();
        this.receiveDelayBatchMap = new HashMap<Integer, Integer>();

    }

    public void leave(Packet packet) {
        this.k++;
        packet.toNext();
    }

    private boolean isFull() {
        return this.k <= 0;
    }

    public int getId() {
        return id;
    }

    private double time(double newTime) {
        if (newTime > this.time) {
            this.time = newTime;
        }
        return this.time;
    }

    private void newCome(Packet packet) {
        logNewCome(packet);
        //Update packet and queue's last time
        packet.update(time(packet.getFinish()));
    }

    private void process(Packet packet) {
        this.k--;
        this.time += packet.getLength() / this.rate;
        packet.update(this.time);logQueueDelay(packet);
    }

    private boolean add(Packet packet) {
        boolean f = false;
        if (isFull()) {
            //Loss packet
            this.nowLossBatchLength++;
            f= false;
        } else {
            process(packet);
            f= true;
        }
        logLoss();
        return f;
    }

    public boolean isLoss(Packet packet) {
        newCome(packet);
        return !add(packet);
    }

    public boolean isFinish(Packet packet) {
        if (isEnd) {
            logFinish(packet);
            return true;
        } else {
            return false;
        }
    }

    private void logFinish(Packet packet) {
        int pathId = packet.getPathId();
        Integer size = this.receiveDelayBatchMap.get(pathId);
        Double nDelay = this.nowDelay.get(pathId);
        if (size == null) {
            size = 1;
        } else {
            size++;
        }
        if(nDelay==null){
            nDelay = 0.0;
        }
        nDelay += packet.getDelay();

        if (size >= Utils.BATCH_SIZE) {
            nDelay /= Utils.BATCH_SIZE;

            List<Double> delayList = this.delayBatchMap.get(pathId);
            if (delayList == null) {
                delayList = new LinkedList<Double>();
                this.delayBatchMap.put(pathId, delayList);
            }
            delayList.add(nDelay);

            size = 0;
            nDelay = 0.0;
        }

        this.receiveDelayBatchMap.put(pathId, size);
        this.nowDelay.put(pathId, nDelay);
    }

    private void logLoss() {
        if (this.receiveLossBatch >= Utils.BATCH_SIZE) {
            this.lossBatchList.add(this.nowLossBatchLength);
            this.nowLossBatchLength = 0;
            this.receiveLossBatch = 0;
        }
    }

    public void getResult() {
        if (!isEnd) {
            //Calculate the Delay time
            double[] res = Analyzer.lossRate(this.lossBatchList);
            System.out.print(this.id);
            System.out.print(" Loss Rate<mean, interval, percentage(");
            System.out.print(res[2]);
            System.out.print(")>:");
            System.out.print(res[0]);
            System.out.print(",");
            System.out.print(res[1]);
            System.out.print(",");
            System.out.print(res[1] / res[0]);
            if(Utils.ALL_BATCHES){
              System.out.print(" Details:");
              System.out.print(Utils.joinLong(this.lossBatchList,","));
            }
            System.out.println();
        } else {
            Map<Integer, Double[]> res = Analyzer.delayTimes(delayBatchMap);
            System.out.print(this.id);
            System.out.print(" Delay Time[<path, mean, interval, percentage>;] {<path>, <number>;}:");
            System.out.print("[");
            List<String> list = new LinkedList<String>();
            List<String> numberList = new LinkedList<String>();
            List<String> detailDelayList = new LinkedList<String>();
            for (Integer i : res.keySet()) {
              StringBuffer sb = new StringBuffer();
              StringBuffer sbNum = new StringBuffer();
              StringBuffer delayDetail = new StringBuffer();
              Double[] r = res.get(i);
              String pathLabel = PATH_MANAGER.getName(i);
              sb.append(pathLabel);
              sbNum.append(pathLabel);
              delayDetail.append(pathLabel);
              delayDetail.append(":<");
              delayDetail.append(Utils.joinDouble(this.delayBatchMap.get(i),","));
              delayDetail.append(">");
              sb.append(",");
              sbNum.append(",");
              sbNum.append(r[2]);
              sb.append(r[0]);
              sb.append(",");
              sb.append(r[1]);
              sb.append(",");
              sb.append(r[1] / r[0]);
              list.add(sb.toString());
              numberList.add(sbNum.toString());
              detailDelayList.add(delayDetail.toString());
            }
            System.out.print(Utils.join(list,";"));
            System.out.print("] {");
            System.out.print(Utils.join(numberList,";"));
            System.out.print("}");
            if(Utils.ALL_BATCHES){
              System.out.print(Utils.join(detailDelayList,";"));
            }
            System.out.println();
        }
    }

    /**
     * Log new come
     */
    private void logNewCome(Packet packet){
      this.receiveLossBatch++;
      this.nowPacketArrivalTime = packet.getFinish();
    }

    private long receiveNum=0;
    private double receiveDelayTime = 0.0;
    private double nowPacketArrivalTime = 0.0;
    private List<Double> queueDelay = new LinkedList<Double>();

    /**
     * Log queue's delay
     */
    private void logQueueDelay(Packet packet){
      this.receiveNum++;
      this.receiveDelayTime += packet.getFinish()-this.nowPacketArrivalTime;
      if(this.receiveNum>=Utils.BATCH_SIZE){
        queueDelay.add(this.receiveDelayTime/Utils.BATCH_SIZE);
        this.receiveNum=0;
        this.receiveDelayTime=0.0;
      }
    }

    public void displayQueueDelay(){
      double[] res = Analyzer.queueDelay(this.queueDelay);
      System.out.print(this.id);
      System.out.print(" Queue Delay<mean,interval,percentage>:");
      System.out.print(res[0]);
      System.out.print(",");
      System.out.print(res[1]);
      System.out.print(",");
      System.out.println(res[1]/res[0]);
    }
}

